import math
from unittest import TestCase

from comp_arch.failures import (
    calculate_annual_failure_rate,
    calculate_failure_rate,
    calculate_mttf,
    calculate_mttf_for_redundancy,
    calculate_mttf_for_subsystem,
    calculate_time_until_fail,
    convert_mttf_or_fit,
)


class TestCalcuateConversions(TestCase):
    def test_calculate_mttf_or_fit_f2m(self):
        mttf = 1000000
        fit = 1000
        arg = fit
        expected = mttf
        result = convert_mttf_or_fit(arg)
        self.assertEqual(result, expected)

    def test_calculate_mttf_or_fit_m2f(self):
        mttf = 1000000
        fit = 1000
        arg = mttf
        expected = fit
        result = convert_mttf_or_fit(arg)
        self.assertEqual(result, expected)


class TestCalcuateFit(TestCase):
    def test_calculate_fit(self):
        system1 = {"name": "foo", "fit": 1000, "count": 1}
        my_systems = [
            system1,
        ]
        expected = 1000
        result = calculate_failure_rate(systems=my_systems)
        self.assertEqual(result, expected)


class TestCalcuateMttf(TestCase):
    def test_calculate_mttf_q1a(self):
        system1 = {"name": "cpu", "fit": 1, "count": 1}
        system2 = {"name": "memory", "fit": 10, "count": 1}
        system3 = {"name": "io", "fit": 100, "count": 1}
        my_systems = [
            system1,
            system2,
            system3,
        ]
        expected = 9009009
        mttf = calculate_mttf(systems=my_systems)
        mttf_floored = math.floor(mttf)
        mttf_rounded = round(mttf, 0)
        self.assertEqual(mttf_floored, expected)
        self.assertEqual(mttf_rounded, expected)

    def test_calculate_mttf_q1a_using_mttf(self):
        system1 = {"name": "cpu", "mttf": 1000000000, "count": 1}
        system2 = {"name": "memory", "mttf": 100000000, "count": 1}
        system3 = {"name": "io", "mttf": 10000000, "count": 1}
        my_systems = [
            system1,
            system2,
            system3,
        ]
        expected = 9009009
        mttf = calculate_mttf(systems=my_systems)
        mttf_floored = math.floor(mttf)
        mttf_rounded = round(mttf, 0)
        self.assertEqual(mttf_floored, expected)
        self.assertEqual(mttf_rounded, expected)

    def test_calculate_failure_rate_page37_example(self):
        system1 = {"name": "disks", "mttf": 1000000, "count": 10}
        system2 = {"name": "ata-controller", "mttf": 500000, "count": 1}
        system3 = {"name": "psu", "mttf": 200000, "count": 1}
        system4 = {"name": "fan", "mttf": 200000, "count": 1}
        system5 = {"name": "ata-cable", "mttf": 1000000, "count": 1}
        my_systems = [
            system1,
            system2,
            system3,
            system4,
            system5,
        ]
        expected = 23000
        result = calculate_failure_rate(systems=my_systems)
        self.assertEqual(result, expected)

    def test_calculate_mttf_page37_example(self):
        failure_rate = 23000
        expected = 43478
        mttf = convert_mttf_or_fit(failure_rate)
        result = math.floor(mttf)
        self.assertEqual(result, expected)

    def test_calculate_mttf_page38_example(self):
        mttf = 200000
        mttr = 24
        expected = 833333333
        mttf, error = calculate_mttf_for_redundancy(mttf, mttr)
        result = math.floor(mttf)
        self.assertTrue(not error, error)
        self.assertEqual(result, expected)

    def test_calculate_mttf_q1B_subsystem(self):
        disks = 4
        mttf = 1000
        mu = disks / mttf
        expected = 4 / 1000
        self.assertEqual(mu, expected)
        fail_threshold = 2
        mttf = fail_threshold * (1 / mu)
        self.assertEqual(mttf, 500)

    def test_calculate_mttf_q1B_overall(self):
        subsystem_mttf = 500
        system1 = {"name": "disks", "mttf": subsystem_mttf, "count": 1}
        system2 = {"name": "processor", "mttf": 10000, "count": 1}
        my_systems = [
            system1,
            system2,
        ]
        expected = 2100000
        expected_mttf = 476
        result = calculate_failure_rate(systems=my_systems)
        self.assertEqual(result, expected)
        mttf = convert_mttf_or_fit(result)
        mttf_f = math.floor(mttf)
        self.assertEqual(mttf_f, expected_mttf)

    def test_calculate_mttf_q1B_noredund_totalfailure(self):
        subsystem_mttf = 1000
        system1 = {"name": "disks", "mttf": subsystem_mttf, "count": 4}
        system2 = {"name": "processor", "mttf": 10000, "count": 1}
        my_systems = [
            system1,
            system2,
        ]
        expected = 4100000
        expected_mttf = 243
        result = calculate_failure_rate(systems=my_systems)
        self.assertEqual(result, expected)
        mttf = convert_mttf_or_fit(result)
        mttf_f = math.floor(mttf)
        self.assertEqual(mttf_f, expected_mttf)

    def test_calculate_mttf_extra1(self):
        system1 = {"name": "foo", "mttf": 1000, "count": 5000}
        my_systems = [
            system1,
        ]
        expected = 0.2  # one every 12 minutes
        result = calculate_mttf(systems=my_systems)
        self.assertEqual(round(result, 1), expected)


class TestFailuresMcases(TestCase):
    def test_calculate_mttf_mAn1a_meta(self):
        system1 = {"name": "devices", "mttf": 1000, "count": 10000}
        my_systems = [
            system1,
        ]
        expected = 0.1  # one every 6 minutes
        result = calculate_mttf(systems=my_systems)
        self.assertEqual(result, expected)

    def test_calculate_mttf_mAn1a_inline(self):
        mttf = 1000
        devices = 10000
        fail_threshold = 9000
        mu = devices / mttf
        system_mttf = fail_threshold * (1 / mu)
        self.assertEqual(system_mttf, 900)
        system1 = {"name": "devices", "mttf": system_mttf, "count": 1}
        my_systems = [
            system1,
        ]
        result = calculate_mttf(systems=my_systems)
        self.assertEqual(math.floor(result), 900)

    def test_calculate_mttf_mAn1a(self):
        mttf = 1000
        devices = 10000
        fail_threshold = 9000
        expected_mttf = 900
        system_mttf = calculate_mttf_for_subsystem(
            number_of_items=devices, mttf=mttf, fail_threshold=fail_threshold
        )
        self.assertEqual(system_mttf, expected_mttf)

    def test_calculate_mttf_mAn1b_meta(self):
        system1 = {"name": "devices", "mttf": 500, "count": 10000}
        my_systems = [
            system1,
        ]
        expected = 0.05  # one every 3 minutes
        result = calculate_mttf(systems=my_systems)
        self.assertEqual(result, expected)

    def test_calculate_mttf_mAn1b(self):
        mu = 20  # devices failing per hour
        system_mttf = 900  # from mAn1a
        devices = mu * system_mttf
        expected_devices = 18000
        fail_threshold = 9000
        self.assertTrue(devices >= fail_threshold)
        self.assertEqual(devices, expected_devices)

    def test_calculate_mttf_mBn1(self):
        mttf = 5000
        devices = 10000
        fail_threshold = 1
        expected_mttf = 0.5
        system_mttf = calculate_mttf_for_subsystem(
            number_of_items=devices, mttf=mttf, fail_threshold=fail_threshold
        )
        self.assertEqual(system_mttf, expected_mttf)

    def test_calculate_mttf_mBn1_before(self):
        system1 = {"name": "devices", "mttf": 5000, "count": 10000}
        my_systems = [
            system1,
        ]
        expected = 0.5  # one every 6 minutes
        result = calculate_mttf(systems=my_systems)
        self.assertEqual(result, expected)

    def test_foo(self):
        count2 = 6666
        count1 = 10000 - count2
        system1 = {"name": "devices", "mttf": 5000, "count": count1}
        system2 = {"name": "devices2", "mttf": 10000, "count": count2}
        my_systems = [
            system1,
            system2,
        ]
        expected = 0.5 * 1.5
        result = calculate_mttf(systems=my_systems)
        self.assertEqual(round(result, 2), expected)


class TestFailures(TestCase):
    def test_calculate_failure_rate_ch1_slide15_by_definition(self):
        servers = 10000
        mttf = 1000
        failure_rate = 10  # per/hour
        assert failure_rate == servers / mttf

    def test_calculate_failure_rate_ch1_slide15_alt1(self):
        servers = 10000
        mttf = 1000
        # failure_rate = 10  # per/hour
        fail_threshold = 9999
        mttf_for_subsystem = 999
        result = calculate_mttf_for_subsystem(
            number_of_items=servers, mttf=mttf, fail_threshold=fail_threshold
        )
        self.assertEqual(math.floor(result), mttf_for_subsystem)

    def test_calculate_failure_rate_ch1_slide15_alt2(self):
        servers = 5000
        mttf = 1000
        # failure_rate = 10  # per/hour
        fail_threshold = 1
        mttf_for_subsystem = 0.2
        result = calculate_mttf_for_subsystem(
            number_of_items=servers, mttf=mttf, fail_threshold=fail_threshold
        )
        self.assertEqual(round(result, 1), mttf_for_subsystem)

    def test_calculate_failure_rate_ch1_slide15(self):
        servers = 10000
        mttf = 1000
        # failure_rate = 10  # per/hour
        fail_threshold = 5000
        mttf_for_subsystem = 500
        result = calculate_mttf_for_subsystem(
            number_of_items=servers, mttf=mttf, fail_threshold=fail_threshold
        )
        self.assertEqual(math.floor(result), mttf_for_subsystem)

    def test_calculate_time_until_fail_ch1_slide15(self):
        failure_rate = 10
        fail_threshold = 5000
        expected = 500
        time_until_fail = calculate_time_until_fail(failure_rate, fail_threshold)
        self.assertEqual(time_until_fail, expected)

    def test_example_page62(self):
        number_of_items = 1000
        time_period = 8760
        mttf = 1000000
        expected = 8.76
        result = calculate_annual_failure_rate(number_of_items, time_period, mttf)
        self.assertEqual(result, expected)


class TestFailuresMcasesActual(TestCase):
    def test_calculate_mttf_q1n1a(self):
        mttf = 500000
        devices = 2
        fail_threshold = 2
        expected_mttf = 500000
        system_mttf = calculate_mttf_for_subsystem(
            number_of_items=devices, mttf=mttf, fail_threshold=fail_threshold
        )
        self.assertEqual(round(system_mttf), expected_mttf)

    def test_calculate_mttf_q1n1b(self):
        mttf = 100000
        devices = 6
        fail_threshold = 2
        expected_mttf = 33333
        system_mttf = calculate_mttf_for_subsystem(
            number_of_items=devices, mttf=mttf, fail_threshold=fail_threshold
        )
        self.assertEqual(round(system_mttf), expected_mttf)

    def test_calculate_mttf_q1n1_overall(self):
        systemA_mttf = calculate_mttf_for_subsystem(
            number_of_items=2, mttf=500000, fail_threshold=2
        )

        systemB_mttf = calculate_mttf_for_subsystem(
            number_of_items=6, mttf=100000, fail_threshold=2
        )

        systemA = {"name": "processor", "mttf": systemA_mttf, "count": 1}
        systemB = {"name": "disks", "mttf": systemB_mttf, "count": 1}
        my_systems = [
            systemB,
            systemA,
        ]
        expected = 32000
        expected_mttf = 31250
        result = calculate_failure_rate(systems=my_systems)
        self.assertEqual(round(result), expected)
        mttf = convert_mttf_or_fit(result)
        self.assertEqual(round(mttf), expected_mttf)


class TestFailuresFcases(TestCase):
    def test_calculate_mttf_fAq5(self):
        system1 = {"name": "devices", "mttf": 1000, "count": 1}
        system2 = {"name": "devices", "mttf": 1000, "count": 10000}
        my_systems = [
            system1,
            system2,
        ]
        expected = 0.1  # one every 6 minutes
        result = calculate_mttf(systems=my_systems)
        self.assertEqual(round(result, 1), expected)

    def test_calculate_mttf_fAq5p2(self):
        system1 = {"name": "devices", "mttf": 2000, "count": 1}
        system2 = {"name": "devices", "mttf": 1000, "count": 10000}
        my_systems = [
            system1,
            system2,
        ]
        expected = 0.1  # one every 6 minutes
        result = calculate_mttf(systems=my_systems)
        self.assertEqual(round(result, 1), expected)

    def test_calculate_mttf_fBq4pa(self):
        htime = 1
        nservers = 10
        system1 = {"name": "app", "mttf": htime, "count": nservers}
        system2 = {"name": "wsc", "mttf": htime, "count": nservers}
        my_systems = [
            system1,
            system2,
        ]
        expected = 0.05  # one every 6 minutes
        result = calculate_mttf(systems=my_systems)
        self.assertEqual(result, expected)

    def test_calculate_mttf_fBq4pa_one_system_only(self):
        htime = 1
        nservers = 10
        system1 = {"name": "app", "mttf": htime, "count": nservers}
        # system2 = {"name": "wsc", "mttf": htime, "count": nservers}
        my_systems = [
            system1,
            # system2,
        ]
        expected = 0.10  # one every 6 minutes
        result = calculate_mttf(systems=my_systems)
        self.assertEqual(result, expected)
